<!doctype html>
<html lang="en">
<head>
    <title>Polyhedra (Three.js)</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link rel=stylesheet href="css/base.css"/>
</head>
<body>

<script src="js/Three58.js"></script>
<script src="js/Detector.js"></script>
<script src="js/TrackballControls.js"></script>
<script src="js/THREEx.KeyboardState.js"></script>
<script src="js/THREEx.FullScreen.js"></script>
<script src="js/THREEx.WindowResize.js"></script>

<script src="js/polyhedra.js"></script>
<!-- GUI for experimenting with values -->		
<script type='text/javascript' src='js/DAT.GUI.min.js'></script>

<!-- Code to display an information button and box when clicked. -->
<script src="js/jquery-1.9.1.js"></script>
<script src="js/jquery-ui.js"></script>
<link rel=stylesheet href="css/jquery-ui.css" />
<link rel=stylesheet href="css/info.css"/>
<script src="js/info.js"></script>
<div id="infoButton"></div>
<div id="infoBox" title="Demo Information">
Controls: 
<ul>
<li>Rotate: Left Mouse Button
<li>Zoom: Mouse Wheel 
</ul>

Data from the website "Virtual Polyhedra: The Encyclopedia of Polyhedra" by George W. Hart
<a href="http://www.georgehart.com/virtual-polyhedra/vp.html">http://www.georgehart.com/virtual-polyhedra/vp.html</a>
<br/><br/> 
This three.js demo is part of a collection at
<a href="http://stemkoski.github.io/Three.js/">http://stemkoski.github.io/Three.js/</a>
</div>
<!-- ------------------------------------------------------------ -->

<div id="ThreeJS" style="position: absolute; left:0px; top:0px"></div>
<script>
/*
	Three.js "tutorials by example"
	Author: Lee Stemkoski
	Date: July 2013 (three.js v59dev)
 */

// MAIN

// standard global variables
var container, scene, camera, renderer, controls;

// custom global variables
var mesh;

init();
animate();

// FUNCTIONS 		
function init() 
{
	// SCENE
	scene = new THREE.Scene();
	// CAMERA
	var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
	var VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 20000;
	camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR);
	scene.add(camera);
	camera.position.set(0,150,400);
	camera.lookAt(scene.position);	
	// RENDERER
	if ( Detector.webgl )
		renderer = new THREE.WebGLRenderer( {antialias:true} );
	else
		renderer = new THREE.CanvasRenderer(); 
	renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
	container = document.getElementById( 'ThreeJS' );
	container.appendChild( renderer.domElement );
	// EVENTS
	THREEx.WindowResize(renderer, camera);
	// CONTROLS
	controls = new THREE.TrackballControls( camera, renderer.domElement );
	controls.noPan = true;
	// LIGHT
	var light = new THREE.PointLight(0xffffff);
	light.position = camera.position;
	scene.add(light);
	// SKYBOX
	var skyBoxGeometry = new THREE.CubeGeometry( 8000, 8000, 8000 );
	var skyBoxMaterial = new THREE.MeshBasicMaterial( { color: 0xccccff, side: THREE.BackSide } );
	var skyBox = new THREE.Mesh( skyBoxGeometry, skyBoxMaterial );
	scene.add(skyBox);
	
	////////////
	// CUSTOM //
	////////////
	
	this.parameters = { transparent: false };
	this.polyhedronMesh = new THREE.Object3D(); 
	this.titleSprite = new THREE.Object3D();	
	scene.add(polyhedronMesh);
	scene.add(titleSprite);
	this.titleSpriteParams = 
	{ 
	    fontsize: 24, 
	    useScreenCoordinates: true, 
	    borderThickness: 2,
	    borderColor: {r:0, g:0, b:0, a:1.0}, 
	    backgroundColor: {r:255, g:255, b:255, a:0.9}, 
	    spriteAlignment: THREE.SpriteAlignment.topCenter 
	}
		
	displayPolyhedron( POLYHEDRA.TruncatedIcosidodecahedron );

	/////////
	// GUI //
	/////////

	gui = new dat.GUI();
	
	gui.add( parameters, "transparent" )
	   .name( "Toggle Transparency" )
	   .onChange( function(value)
		{
			polyhedronMesh.children[2].material.transparent = value;
		});
	
	categoryFolders = {};
	// check category[0]. if folder does not exist, create it, add it to that folder.
	//   if category field DNE, just add it to the end.
	for (var arg in POLYHEDRA)
	{
		var guiSection = gui;
		if ( POLYHEDRA[arg].hasOwnProperty("category") )
		{
			var category = POLYHEDRA[arg].category[0];
			if ( !categoryFolders.hasOwnProperty(category) )
				categoryFolders[category] = gui.addFolder(category);
			guiSection = categoryFolders[category];
		}
		
        parameters[arg] = function() {};
		
		guiSection.add( parameters, arg )
		.name( POLYHEDRA[arg].name )
		.onChange( function(value)
		{
		   displayPolyhedron( POLYHEDRA[this.property] );
		});
	}
	gui.open();	
	
} // end of function init()

var faces;

function displayPolyhedron(data)
{
	scene.remove(polyhedronMesh);
	scene.remove(titleSprite);
	polyhedronMesh = polyhedronDataToMesh(data);
	titleSprite = makeTextSprite( "  " + data.name + "  ",
	    titleSpriteParams );
	titleSprite.position.set(window.innerWidth / 2, 10, 0);
	scene.add(titleSprite);
	scene.add(polyhedronMesh);
}

function polyhedronDataToMesh(data)
{
	var polyhedron = new THREE.Object3D();
	
	// convert vertex data to THREE.js vectors
	var vertex = [] 
	for (var i = 0; i < data.vertex.length; i++)
		vertex.push( new THREE.Vector3( data.vertex[i][0], data.vertex[i][1], data.vertex[i][2] ).multiplyScalar(100) );

	var vertexGeometry = new THREE.SphereGeometry( 6, 12, 6 );
	var vertexMaterial = new THREE.MeshLambertMaterial( { color: 0x222244 } );
	var vertexSingleMesh = new THREE.Mesh( vertexGeometry );

	var vertexAmalgam = new THREE.Geometry();
	for (var i = 0; i < data.vertex.length; i++)
	{
		var vMesh = vertexSingleMesh.clone();
		vMesh.position = vertex[i];
		THREE.GeometryUtils.merge( vertexAmalgam, vMesh );
	}
	var vertexMesh = new THREE.Mesh( vertexAmalgam, vertexMaterial );
	polyhedron.add( vertexMesh );
	
	// convert edge data to cylinders
	var edgeMaterial = new THREE.MeshLambertMaterial( {color: 0x666666} );
	var edgeAmalgam = new THREE.Geometry();
	for (var i = 0; i < data.edge.length; i++)
	{
		var index0 = data.edge[i][0];
		var index1 = data.edge[i][1];
		var eMesh = cylinderMesh( vertex[index0], vertex[index1], edgeMaterial );
		THREE.GeometryUtils.merge( edgeAmalgam, eMesh );
	}
	var edgeMesh = new THREE.Mesh( edgeAmalgam, edgeMaterial );
	polyhedron.add( edgeMesh );
	
	// convert face data to a single (triangulated) geometry
	var faceMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors, side: THREE.FrontSide, transparent:parameters.transparent, opacity:0.8 } );
	var faceColors = 
	{
	    3: new THREE.Color( 0xcc0000 ),
	    4: new THREE.Color( 0x00cc00 ),
	    5: new THREE.Color( 0x0000cc ),
	    6: new THREE.Color( 0xcccc00 ),
	    7: new THREE.Color( 0x999999 ),
	    8: new THREE.Color( 0x990099 ),
	    9: new THREE.Color( 0xff6600 ),
	    10: new THREE.Color( 0x6666ff )
	};
	
	var geometry = new THREE.Geometry();
	geometry.vertices = vertex;
	var faceIndex = 0;
	for (var faceNum = 0; faceNum < data.face.length; faceNum++)
	{
		for (var i = 0; i < data.face[faceNum].length - 2; i++)
		{
			geometry.faces[faceIndex] = new THREE.Face3( data.face[faceNum][0], data.face[faceNum][i+1], data.face[faceNum][i+2] );
			geometry.faces[faceIndex].color = faceColors[data.face[faceNum].length];
			faceIndex++;
		}
	}
	
	geometry.computeFaceNormals();
	geometry.computeVertexNormals();

	faces = new THREE.Mesh(geometry, faceMaterial);
	faces.scale.multiplyScalar(1.01);
	polyhedron.add(faces);
	
	var interiorMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors, side: THREE.BackSide } );
	
	var interiorFaces = new THREE.Mesh(geometry, interiorMaterial);
	interiorFaces.scale.multiplyScalar(0.99);
	polyhedron.add( interiorFaces );
	
	return polyhedron;
}

function cylinderMesh(point1, point2, material)
{
    var direction = new THREE.Vector3().subVectors(point2, point1);
    var arrow = new THREE.ArrowHelper(direction.clone().normalize(), point1);
    var rotation = new THREE.Vector3().setEulerFromQuaternion(arrow.quaternion);
    var edgeGeometry = new THREE.CylinderGeometry( 2, 2, direction.length(), 8, 4 );
    var edge = new THREE.Mesh(edgeGeometry, material); 
    edge.position = new THREE.Vector3().addVectors(point1, direction.multiplyScalar(0.5));
	edge.rotation = rotation;
	return edge;

	// the result should align with:
	//   scene.add( new THREE.ArrowHelper( direction.clone().normalize(), point1, direction.length()) );
}

function animate() 
{
    requestAnimationFrame( animate );
	render();		
	update();
}

function update()
{
	if (camera.position.length() < 150) 
		camera.position.setLength(150); 
	if (camera.position.length() > 500)
		camera.position.setLength(500);
	
	controls.update();
}

function render() 
{
	renderer.render( scene, camera );
}

function makeTextSprite( message, parameters )
{
	if ( parameters === undefined ) parameters = {};
	
	var fontface = parameters.hasOwnProperty("fontface") ? 
		parameters["fontface"] : "Arial";
	
	var fontsize = parameters.hasOwnProperty("fontsize") ? 
		parameters["fontsize"] : 18;
	
	var borderThickness = parameters.hasOwnProperty("borderThickness") ? 
		parameters["borderThickness"] : 4;
	
	var borderColor = parameters.hasOwnProperty("borderColor") ?
		parameters["borderColor"] : { r:0, g:0, b:0, a:1.0 };
	
	var backgroundColor = parameters.hasOwnProperty("backgroundColor") ?
		parameters["backgroundColor"] : { r:255, g:255, b:255, a:1.0 };

	var useScreenCoordinates = parameters.hasOwnProperty("useScreenCoordinates") ?
		parameters["useScreenCoordinates"] : false;
	
	var spriteAlignment = parameters.hasOwnProperty("spriteAlignment") ?
		parameters["spriteAlignment"] : THREE.SpriteAlignment.topLeft;
		
	// create canvas, resize later.
	var canvas = document.createElement('canvas');
	var context = canvas.getContext('2d');
	context.font = "Bold " + fontsize + "px " + fontface;
    
	// get size data (height depends only on font size)
	var metrics = context.measureText( message );
	var textWidth = metrics.width;
	
	// calculate correct dimensions of canvas and resize
	var imageWidth = textWidth + borderThickness * 2;
	var imageHeight = fontsize * 1.44 + borderThickness * 2;
	canvas.width = imageWidth;
	canvas.height = imageHeight;
	// new canvas, new context.
	context = canvas.getContext('2d');
	context.font = "Bold " + fontsize + "px " + fontface;
	
	// background color
	context.fillStyle   = "rgba(" + backgroundColor.r + "," + backgroundColor.g + ","
								  + backgroundColor.b + "," + backgroundColor.a + ")";
	// border color
	context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + ","
								  + borderColor.b + "," + borderColor.a + ")";

	context.lineWidth = borderThickness;
	roundRect(context, borderThickness/2, borderThickness/2, textWidth + borderThickness, fontsize * 1.4 + borderThickness, 6);
	// 1.4 is extra height factor for text below baseline: g,j,p,q.
	
	// text color
	context.fillStyle = "rgba(0, 0, 0, 1.0)";

	context.fillText( message, borderThickness, fontsize + borderThickness );
	
	// canvas contents will be used for a texture
	var texture = new THREE.Texture(canvas) 
	texture.needsUpdate = true;

	var spriteMaterial = new THREE.SpriteMaterial( 
		{ map: texture, useScreenCoordinates: useScreenCoordinates, alignment: spriteAlignment } );
	var sprite = new THREE.Sprite( spriteMaterial );
	sprite.scale.set(imageWidth, imageHeight, 1.0);
	sprite.width = imageWidth;
	sprite.height = imageHeight;
	return sprite;	
}

// function for drawing rounded rectangles
function roundRect(ctx, x, y, w, h, r) 
{
    ctx.beginPath();
    ctx.moveTo(x+r, y);
    ctx.lineTo(x+w-r, y);
    ctx.quadraticCurveTo(x+w, y, x+w, y+r);
    ctx.lineTo(x+w, y+h-r);
    ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
    ctx.lineTo(x+r, y+h);
    ctx.quadraticCurveTo(x, y+h, x, y+h-r);
    ctx.lineTo(x, y+r);
    ctx.quadraticCurveTo(x, y, x+r, y);
    ctx.closePath();
    ctx.fill();
	ctx.stroke();   
}

</script>

<!-- override dat.gui styles -->
<style>.dg .property-name { width: 95% }</style>

</body>
</html>
